package com.info3k.server;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.info3k.client.Info3KService;
import com.info3k.client.domain.Address;
import com.info3k.client.domain.Category;
import com.info3k.client.domain.Event;
import com.info3k.client.domain.Location;
import com.info3k.client.domain.User;
import com.info3k.server.domain.AddressP;
import com.info3k.server.domain.CategoryP;
import com.info3k.server.domain.EventP;
import com.info3k.server.domain.LocationP;
import com.info3k.server.domain.UserP;
import com.info3k.server.utils.Tools;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class Info3KServiceImpl extends RemoteServiceServlet implements
		Info3KService {

	private PersistenceManager persistence = PMF.get().getPersistenceManager();

	public Boolean isAvailableUsername(String username) {
		String query = "select from " + UserP.class.getName() + " where username == '" + username + "'";
        List<UserP> users = (List<UserP>) persistence.newQuery(query).execute();
		return users.size()==0;
	}
	
	public Boolean registerUser(User u, String response, String challenge){
		ReCaptcha r = ReCaptchaFactory.newReCaptcha("6Ld_8AkAAAAAACa_FB4gLUXuae28YVOHTucoBFry", "6Ld_8AkAAAAAAPdacstWBZg7EDcYdjyzpoG_vudO", true);
		if(r.checkAnswer(getThreadLocalRequest().getRemoteAddr().toString(), challenge, response).isValid()){
			UserP user = new UserP(u);
			
			user.setActivated(false);
			user.setPassword(Tools.getMD5(user.getPassword()));
			
			EmailService emailService = new EmailService();
			emailService.sendEmail("Registration", "<p>Click here to confirm your email address: <a href=\"http://info-3k.appspot.com/servlet/ActivateUser?username=" + user.getUsername() + "&registrationKey=" + user.getId().getId() + "\">Complete registrations</a></p>", user.getEmail(), user.getUsername());

			persistence.makePersistent(user);
			return true;
		}
        return false;
	}

	public User logInUser(String username, String password) {
		String query = "select from " + UserP.class.getName() + " where username == '" + username + "' && password == '" + Tools.getMD5(password) + "'";
        List<UserP> users = (List<UserP>) persistence.newQuery(query).execute();
        if(users.size()>0) {
        	if(!users.get(0).getActivated()) return null;
        	getThreadLocalRequest().getSession().setAttribute("user", UserP.createUser(getUserByKey(users.get(0).getId())));
        	return UserP.createUser(users.get(0));
        } else return null;
	}
	
	public Boolean logOutUser(User u){
		getThreadLocalRequest().getSession().removeAttribute("user");
		return true;
	}

	public User checkIfLogged() {
		if(getThreadLocalRequest().getSession().getAttribute("user")!=null){
			User u = (User)getThreadLocalRequest().getSession().getAttribute("user");
			Key id = KeyFactory.createKey(UserP.class.getSimpleName(), u.getId());
			
			User newUser = UserP.createUser(getUserByKey(id));
			getThreadLocalRequest().getSession().setAttribute("user", newUser);
			return newUser;
		} else return null;
	}
	
	public void addLocation(Location location) {	
		LocationP loc = new LocationP(location);
		
		persistence.makePersistent(loc);
	}
	
	public void addAddress(Address address) {
		
		AddressP add = new AddressP(address);
		
		persistence.makePersistent(add);
	}
	
	public List<Address> getAddressesByFilter(String filter, Integer pageSize, Integer current) {
		String query = null;
		if(current==null) query = "select from " + AddressP.class.getName() + " order by id desc";
			else query = "select from " + AddressP.class.getName() + " order by id desc range " + (current*pageSize) + "," + (current*pageSize + pageSize) ;
        List<AddressP> addresses = (List<AddressP>) persistence.newQuery(query).execute();
        
        List<Address> newAddresses = new ArrayList<Address>();
        if(addresses.size()<pageSize) pageSize = addresses.size();
        for(int i = 0; i < pageSize; i++) {
			if(filter!=null){
				if(addresses.get(i).getAddress().toUpperCase().contains(filter.toUpperCase())){
					Address add = AddressP.createAddress(addresses.get(i));
					
		        	UserP userP = getUserByKey(addresses.get(i).getUser());
					
		        	if(userP!=null){
						add.setUser(UserP.createUser(userP));
					}
		        	
					newAddresses.add(add);
				}
			}
		}
        
        if(newAddresses.size()>0 && current==null) {
        	newAddresses.get(0).setLength(addresses.size());
        }
        
        return newAddresses;
	}
	
	public List<Address> getAddressesByUser(User user, Integer pageSize, Integer current) {
		UserP u = new UserP(user);

		Query q1 = null; 
		if(current==null) q1 = persistence.newQuery("select from " + AddressP.class.getName() + " where user == :keys");
			else q1 = persistence.newQuery("select from " + AddressP.class.getName() + " where user == :keys range " + (current*pageSize) + "," + (current*pageSize + pageSize));
		//q1.setOrdering("id desc");
		List<AddressP> addressesP = (List<AddressP>)q1.execute(u.getId());

    	List<Address> l = new ArrayList<Address>();
    	if(addressesP.size()<pageSize) pageSize = addressesP.size();
        for(int i = 0; i < pageSize; i++) {
			Address add = AddressP.createAddress(addressesP.get(i));
			add.setUser(user);
    		l.add(add);
		}
        
        if(l.size()>0 && current==null) {
        	l.get(0).setLength(addressesP.size());
        }
        
        return l;
	}
	
	public List<Location> getLocationsByUser(User user, Integer pageSize, Integer current) {
		UserP u = new UserP(user);

		Query q1 = null; 
		if(current==null) q1 = persistence.newQuery("select from " + LocationP.class.getName() + " where user == :keys");
			else q1 = persistence.newQuery("select from " + LocationP.class.getName() + " where user == :keys range " + (current*pageSize) + "," + (current*pageSize + pageSize));
		//q1.setOrdering("id desc");
    	List<LocationP> locationsP = (List<LocationP>)q1.execute(u.getId());

    	List<Location> l = new ArrayList<Location>();
    	for (LocationP locationP : locationsP) {
			Location loc = LocationP.createLocation(locationP);
			
			CategoryP categoryP = null;
			if(locationP.getCategory()!=null)
        		categoryP = getCategoryByKey(locationP.getCategory());
        	
        	if(categoryP!=null){
				loc.setCategory(CategoryP.createCategory(categoryP));
			}
			
			loc.setUser(user);
    		l.add(loc);
		}

        if(l.size()>0 && current==null) {
        	l.get(0).setLength(locationsP.size());
        }
        
        return l;
	}
	
	public List<Location> getLocationsByFilter(String filter, Category category, Integer pageSize, Integer current) {
		String query = null;
		
		if(category!=null) {
			if(current==null) query = "select from " + LocationP.class.getName() + " where category == :keys order by id desc";
				else query = "select from " + LocationP.class.getName() + " where category == :keys order by id desc range " + (current*pageSize) + "," + (current*pageSize + pageSize) ;
		} else {
			if(current==null) query = "select from " + LocationP.class.getName() + " order by id desc";
				else query = "select from " + LocationP.class.getName() + " order by id desc range " + (current*pageSize) + "," + (current*pageSize + pageSize) ;			
		}
		
		List<Location> newLocations = new ArrayList<Location>();
		
		try{
			List<LocationP> locations = null;
			if(category!=null){
				Key key = KeyFactory.createKey(CategoryP.class.getSimpleName(), category.getId());
				locations = (List<LocationP>) persistence.newQuery(query).execute(key);
			} else locations = (List<LocationP>) persistence.newQuery(query).execute();
			
	        for (LocationP location : locations) {
	        	if(filter!=null){
					if(location.getLocationName().toUpperCase().contains(filter.toUpperCase())){
			        	Location loc = LocationP.createLocation(location);
						
			        	UserP userP = getUserByKey(location.getUser());
			        	
						CategoryP categoryP = null;
						if(location.getCategory()!=null)
			        		categoryP = getCategoryByKey(location.getCategory());
			        	
			        	if(categoryP!=null){
							loc.setCategory(CategoryP.createCategory(categoryP));
						}
			        	
			        	if(location.getAddresses()!=null && location.getAddresses().size()>0){
				        	loc.setAddresses(getAddressesByKeys(location.getAddresses()));
			        	}
			        	
			        	if(userP!=null){
				        	User u = UserP.createUser(userP);
			        		loc.setUser(u);
							for (Address a : loc.getAddresses()) {
								a.setUser(u);
							}
						}
			        	
			        	newLocations.add(loc);
					}
				}
			}
	        
	        if(newLocations.size()>0 && current==null) {
	        	newLocations.get(0).setLength(locations.size());
	        }
	        
		} catch (Exception e) {
			log("ERROR", e);
		}
		
		return newLocations;
	}
	
	public List<Address> getLatestAddress(int count){
		String query = "select from " + AddressP.class.getName() + " order by id desc range 0," + count;
        List<AddressP> addresses = (List<AddressP>) persistence.newQuery(query).execute();
        
        List<Address> newAddresses = new ArrayList<Address>();
        for (AddressP address : addresses) {
			Address add = AddressP.createAddress(address);
			
        	UserP userP = getUserByKey(address.getUser());
			
        	if(userP!=null){
				add.setUser(UserP.createUser(userP));
			}
			newAddresses.add(add);
		}
        
        return newAddresses;
	}
	
	public List<User> getLatestUsers(int count){
		String query = "select from " + UserP.class.getName() + " order by id desc range 0," + count;
        List<UserP> users = (List<UserP>) persistence.newQuery(query).execute();
        
        List<User> newUsers = new ArrayList<User>();
        for (UserP user : users) {
			User u = UserP.createUser(user);
			if(u.getActivated()!=null && u.getActivated())
				newUsers.add(u);
		}
        
        return newUsers;
	}
	
	public List<Location> getLatestLocations(int count){
		String query = "select from " + LocationP.class.getName() + " order by id desc range 0," + count;
        List<LocationP> locations = (List<LocationP>) persistence.newQuery(query).execute();
        
        List<Location> newLocations = new ArrayList<Location>();
        for (LocationP location : locations) {
			
        	Location loc = LocationP.createLocation(location);
			
        	if(location.getAddresses()!=null && location.getAddresses().size()>0){
	        	loc.setAddresses(getAddressesByKeys(location.getAddresses()));
        	}
        	
        	UserP userP = getUserByKey(location.getUser());
        				
        	if(userP!=null){
				User u = UserP.createUser(userP);
        		loc.setUser(u);
				for (Address a : loc.getAddresses()) {
					a.setUser(u);
				}
			}
        	
			CategoryP categoryP = null;
			if(location.getCategory()!=null)
        		categoryP = getCategoryByKey(location.getCategory());
        	
        	if(categoryP!=null){
				loc.setCategory(CategoryP.createCategory(categoryP));
			}
			
        	newLocations.add(loc);
		}
        
        return newLocations;
	}
	
	private UserP getUserByKey(Key key){
    	Query q = persistence.newQuery("select from " + UserP.class.getName() + " where id == :keys");
    	
    	List<UserP> usersP = (List<UserP>)q.execute(key);
    	
    	if(usersP.size()>0) return usersP.get(0); 
    	return null;
	}
	
	private CategoryP getCategoryByKey(Key key){
    	Query q = persistence.newQuery("select from " + CategoryP.class.getName() + " where id == :keys");
    	
    	List<CategoryP> categoriesP = (List<CategoryP>)q.execute(key);
    	
    	if(categoriesP.size()>0) return categoriesP.get(0); 
    	return null;
	}
	
	private Address[] getAddressesByKeys(Set<Key> list) {
    	Query q1 = persistence.newQuery("select from " + AddressP.class.getName() + " where id == :keys");
    	
    	List<AddressP> addressesP = (List<AddressP>)q1.execute(list);
    	
    	Address[] l = new Address[addressesP.size()];
    	
    	int i = 0;
    	for (AddressP addressP : addressesP) {
			l[i] = AddressP.createAddress(addressP);
			i++;
		}
    	
    	return l;
	}

	private Location[] getLocationsByKeys(Set<Key> list) {
    	Query q1 = persistence.newQuery("select from " + LocationP.class.getName() + " where id == :keys");
    	
    	List<LocationP> locationsP = (List<LocationP>)q1.execute(list);
    	
    	Location[] l = new Location[locationsP.size()];
    	
    	int i = 0;
    	for (LocationP locationP : locationsP) {
			l[i] = LocationP.createLocation(locationP);
			i++;
		}
    	
    	return l;
	}
	
	private Location[] getLocationsIncludeAddressesByKeys(Set<Key> list) {
    	Query q1 = persistence.newQuery("select from " + LocationP.class.getName() + " where id == :keys");
    	
    	List<LocationP> locationsP = (List<LocationP>)q1.execute(list);
    	
    	Location[] l = new Location[locationsP.size()];
    	
    	int i = 0;
    	for (LocationP locationP : locationsP) {
			l[i] = LocationP.createLocation(locationP);
			
			if(locationP.getAddresses()!=null && locationP.getAddresses().size()>0){
		    	l[i].setAddresses(getAddressesByKeys(locationP.getAddresses()));
			}
			i++;
		}
    	
    	return l;
	}
	
	public void infoServer(String s) {
	}

	public void addCategory(Category cat) {
		CategoryP catP = new CategoryP(cat);
		
		persistence.makePersistent(catP);
	}

	public List<Category> getAllCategories() {
		String query = "select from " + CategoryP.class.getName() + " order by id desc";
        List<CategoryP> categories = (List<CategoryP>) persistence.newQuery(query).execute();
        
        List<Category> newCategories = new ArrayList<Category>();
        for (CategoryP cat : categories) {
			Category c = CategoryP.createCategory(cat);
			
			newCategories.add(c);
		}
        return newCategories;
	}

	public void addEvent(Event event) {
		EventP eventP = new EventP(event);
		
		persistence.makePersistent(eventP);
	}

	public List<Event> getLatestEvents(int count) {
		String query = "select from " + EventP.class.getName() + " order by id desc range 0," + count;
        List<EventP> events = (List<EventP>) persistence.newQuery(query).execute();
        
        List<Event> newEvents = new ArrayList<Event>();
        for (EventP event : events) {
			
        	Event ev = EventP.createEvent(event);
			
        	if(event.getLocations()!=null && event.getLocations().size()>0){
	        	ev.setLocations(getLocationsIncludeAddressesByKeys(event.getLocations()));
        	}
        	
        	UserP userP = getUserByKey(event.getUser());
        				
        	if(userP!=null){
				ev.setUser(UserP.createUser(userP));
			}
        	
			if(ev.getLocations()!=null){
	        	for (Location loc : ev.getLocations()) {
					Key key = KeyFactory.createKey(LocationP.class.getSimpleName(), loc.getId());
					CategoryP categoryP = getCategoryByKey(key);
					
					if(categoryP!=null){
						loc.setCategory(CategoryP.createCategory(categoryP));
					}
		        	
		        	if(userP!=null){
		        		loc.setUser(UserP.createUser(userP));
					}
				}
			}
			
        	newEvents.add(ev);
		}
        return newEvents;
	}
	
	public void rateEvent(Event event, Double rate){
		Double newRate = ((event.getNumRates() * event.getRate()) + rate) / (event.getNumRates()+1);
		event.setNumRates(event.getNumRates() + 1);
		event.setRate(newRate);
		
		addEvent(event);
	}
	
	public void viewEvent(Event event){
		event.setViews(event.getViews() + 1);
		addEvent(event);
	}

	public void rateLocation(Location loc, Double rate) {
		if(loc.getNumRates()==null) loc.setNumRates(0L);
		if(loc.getRate()==null) loc.setRate(0.);
		if(loc.getViews()==null) loc.setViews(0L);
		Double newRate = ((loc.getNumRates() * loc.getRate()) + rate) / (loc.getNumRates()+1);
		loc.setNumRates(loc.getNumRates() + 1);
		loc.setRate(newRate);
		
		addLocation(loc);
	}

	public void viewLocation(Location loc) {
		if(loc.getViews()==null) loc.setViews(0L);
		loc.setViews(loc.getViews() + 1);
		addLocation(loc);
	}

	public List<Event> getEventsByFilter(String filter, Integer pageSize, Integer current) {

		String query = null;
		if(current==null) query = "select from " + EventP.class.getName() + " order by id desc";
			else query = "select from " + EventP.class.getName() + " order by id desc range " + (current*pageSize) + "," + (current*pageSize + pageSize) ;
		
        List<EventP> events = (List<EventP>) persistence.newQuery(query).execute();
        
        List<Event> newEvents = new ArrayList<Event>();
        for (EventP event : events) {
        	if(event.getTitle().toUpperCase().contains(filter.toUpperCase()) ||
        	   event.getDescription().getValue().toUpperCase().contains(filter.toUpperCase())){
		        	Event ev = EventP.createEvent(event);
					
		        	if(event.getLocations()!=null && event.getLocations().size()>0){
			        	ev.setLocations(getLocationsIncludeAddressesByKeys(event.getLocations()));
		        	}
		        	
		        	UserP userP = getUserByKey(event.getUser());
		        				
		        	if(userP!=null){
						ev.setUser(UserP.createUser(userP));
					}
		        	
					if(ev.getLocations()!=null){
			        	for (Location loc : ev.getLocations()) {
							Key key = KeyFactory.createKey(LocationP.class.getSimpleName(), loc.getId());
							CategoryP categoryP = getCategoryByKey(key);
							
							if(categoryP!=null){
								loc.setCategory(CategoryP.createCategory(categoryP));
							}
				        	
				        	if(userP!=null){
				        		loc.setUser(UserP.createUser(userP));
							}
						}
					}
		        	newEvents.add(ev);
        	}
		}
        
        if(newEvents.size()>0 && current==null) {
        	newEvents.get(0).setLength(events.size());
        }
        
        return newEvents;
	}

	public List<Event> getEventsByUser(User user, Integer pageSize, Integer current) {
		UserP u = new UserP(user);
		
		Query q1 = null; 
		if(current==null) q1 = persistence.newQuery("select from " + EventP.class.getName() + " where user == :keys");
			else q1 = persistence.newQuery("select from " + EventP.class.getName() + " where user == :keys range " + (current*pageSize) + "," + (current*pageSize + pageSize));
		//q1.setOrdering("id desc");
        List<EventP> events = (List<EventP>) q1.execute(u.getId());
        
        List<Event> newEvents = new ArrayList<Event>();
        for (EventP event : events) {
        	Event ev = EventP.createEvent(event);
			
        	if(event.getLocations()!=null && event.getLocations().size()>0){
	        	ev.setLocations(getLocationsIncludeAddressesByKeys(event.getLocations()));
        	}
        	
        	UserP userP = getUserByKey(event.getUser());
        				
        	if(userP!=null){
				ev.setUser(UserP.createUser(userP));
			}
        	
			if(ev.getLocations()!=null){
	        	for (Location loc : ev.getLocations()) {
					Key key = KeyFactory.createKey(LocationP.class.getSimpleName(), loc.getId());
					CategoryP categoryP = getCategoryByKey(key);
					
					if(categoryP!=null){
						loc.setCategory(CategoryP.createCategory(categoryP));
					}
		        	
		        	if(userP!=null){
		        		loc.setUser(UserP.createUser(userP));
					}
				}
			}
        	newEvents.add(ev);
		}
        
        if(newEvents.size()>0 && current==null) {
        	newEvents.get(0).setLength(events.size());
        }
        
        return newEvents;
	}

	public void addUser(User u) {
		UserP user = new UserP(u);
		
		persistence.makePersistent(user);
	}
	
	public User changePasswordForUser(User u, String oldPassword, String newPassword) {
		UserP user = new UserP(u);
		
		if(!user.getPassword().equals(Tools.getMD5(oldPassword))) return null;

		user.setPassword(Tools.getMD5(newPassword));
		
		persistence.makePersistent(user);
		
		return UserP.createUser(user);
	}

	public Boolean recoverPassword(String username, String response, String challenge) {
		ReCaptcha r = ReCaptchaFactory.newReCaptcha("6Ld_8AkAAAAAACa_FB4gLUXuae28YVOHTucoBFry", "6Ld_8AkAAAAAAPdacstWBZg7EDcYdjyzpoG_vudO", true);
		if(r.checkAnswer(getThreadLocalRequest().getRemoteAddr().toString(), challenge, response).isValid()){
			String query = "select from " + UserP.class.getName() + " where username == '" + username + "'";
	        List<UserP> users = (List<UserP>) persistence.newQuery(query).execute();
	        if(users.size()>0) {
	        	UserP u = users.get(0);
	        	
				if(!u.getActivated()) return null;
	        	
				EmailService emailService = new EmailService();
				emailService.sendEmail("Reset password", "<p>Click here to reset your password: <a href=\"http://info-3k.appspot.com/servlet/ResetPassword?username=" + u.getUsername() + "&recoveryKey=" + u.getId().getId() + "\">Reset password</a></p>", u.getEmail(), u.getUsername());
	        	
	        	return true;
	        } else return null;
		} else return false;
	}
}